Amazon LexにCloudFormationでLambda関数を関連付ける場合、リソースベースポリシーも記載が必要です

Amazon LexにCloudFormationでLambda関数を関連付ける場合、リソースベースポリシーも記載が必要です

Clock Icon2024.09.17

はじめに

以前、Amazon Connectに、Amazon LexボットとAWS Lambda関数をAWS CloudFormationや AWS CLIで関連付け方法をブログで紹介しました。

https://dev.classmethod.jp/articles/associate-lex-lambda-with-amazon-connect-using-cloudformation-cli/

この記事を参考に、CloudFormationでLexボットとLexから呼び出すLambdaを作成しようとした際、LexボットがLambdaを呼び出すためには、Lambdaのリソースベースポリシーを明示的にCloudFormationテンプレートに記載する必要がわかりました。

cm-hirai-screenshot 2024-09-17 8.34.41
この追加設定が必要な理由と具体的な設定内容を紹介します。

CloudFormationで作成してみる

以下にCloudFormationテンプレートを紹介します。
なお、Connectインスタンスは事前に作成済みであることを前提としています。
テンプレートの主な内容は次の通りです。

  • 以下を構築
    • Lexボット
      • ボット、バージョン、エイリアス
      • Lexボットが使用するロググループとS3バケット
    • Lambda
      • Lexボットから呼び出されるLambda
    • IAMロール
      • Lexボット用IAMロール
      • Lambda用IAMロール
  • ConnectからLexボットを呼ぶため、LexをConnectインスタンスに関連づける
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Create Lambda, Lex bot, and associate them with Connect instance'

Parameters:
  ConnectInstanceARN:
    Type: String
    Description: The Amazon Resource Name (ARN) of the instance.
  ProjectName:
    Type: String

Resources:
  TestLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${ProjectName}-test-lambda
      Handler: index.lambda_handler
      Role: !GetAtt TestLambdaExecutionRole.Arn
      Code:
        ZipFile: |
          ~省略~

      Runtime: python3.12
      MemorySize: 512
      Timeout: 10

  TestLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${ProjectName}-test-lambda-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  TestLex:
    Type: AWS::Lex::Bot
    Properties:
      Name: !Sub ${ProjectName}-test-lex
      DataPrivacy:
        ChildDirected: false
      IdleSessionTTLInSeconds: 300
      RoleArn: !GetAtt TestLexRole.Arn
      BotLocales:
        - LocaleId: ja_JP
          NluConfidenceThreshold: 0.40
          VoiceSettings:
            VoiceId: Kazuha
          Intents:
            - Name: FallbackIntent
              ParentIntentSignature: AMAZON.FallbackIntent
              DialogCodeHook:
                Enabled: true
            - Name: dummy
              SampleUtterances:
                - Utterance: dummy
              DialogCodeHook:
                Enabled: true

  TestLexVersion1:
    Type: AWS::Lex::BotVersion
    Properties:
      BotId: !Ref TestLex
      BotVersionLocaleSpecification:
        - LocaleId: ja_JP
          BotVersionLocaleDetails:
            SourceBotVersion: DRAFT
  TestLexAlias:
    Type: AWS::Lex::BotAlias
    Properties:
      BotAliasLocaleSettings:
        - LocaleId: ja_JP
          BotAliasLocaleSetting:
            Enabled: true
            CodeHookSpecification:
              LambdaCodeHook:
                LambdaArn: !GetAtt TestLambdaFunction.Arn
                CodeHookInterfaceVersion: '1.0'
      BotAliasName: dev
      BotId: !Ref TestLex
      BotVersion: !GetAtt TestLexVersion1.BotVersion
      ConversationLogSettings:
        TextLogSettings:
          - Enabled: true
            Destination:
              CloudWatch:
                CloudWatchLogGroupArn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${TestLexCloudWatchLogGroup}'
                LogPrefix: dev/
        AudioLogSettings:
          - Enabled: true
            Destination:
              S3Bucket:
                S3BucketArn: !GetAtt TestLexS3Bucket.Arn
                LogPrefix: dev/
  TestLexCloudWatchLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lex/${ProjectName}-test-lex
      RetentionInDays: 365
  TestLexS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${ProjectName}-test-lex-${AWS::AccountId}

  TestLexRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${ProjectName}-test-lex-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lexv2.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: !Sub ${ProjectName}-test-lex-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - polly:SynthesizeSpeech
                Resource: '*'
              - Effect: Allow
                Action:
                  - s3:PutObject
                Resource:
                  - !Sub 'arn:aws:s3:::${TestLexS3Bucket}/*'
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${TestLexCloudWatchLogGroup}:*'

  TestLexAssociation:
    Type: AWS::Connect::IntegrationAssociation
    Properties:
      InstanceId: !Ref ConnectInstanceARN
      IntegrationType: LEX_BOT
      IntegrationArn: !GetAtt TestLexAlias.Arn

  # Lambdaのリソースベースポリシー
  # TestLambdaPermission:
  #   Type: AWS::Lambda::Permission
  #   Properties:
  #     Action: lambda:InvokeFunction
  #     FunctionName: !GetAtt TestLambdaFunction.Arn
  #     Principal: lexv2.amazonaws.com
  #     SourceArn: !GetAtt TestLexAlias.Arn
  #     SourceAccount: !Ref AWS::AccountId

このテンプレートを使用してCloudFormationスタックを作成し、ConnectフローからLexを呼び出しても、フローはエラー状態に遷移してしまいます。

エラーの原因を特定するため、以下のログを確認しましたが、明確な理由は判明しませんでした。

  • Connectフローログ:具体的な理由は記載されず、"Error"しか出力されません。
  • Lexのロググループ:ログの出力がありません。
  • Lexが呼び出すLambdaのロググループ:ログの出力がありません。

さらに調査を進め、CloudTrailのLambdaデータイベントを確認したところ、LexからLambdaを呼び出す際(Invoke API使用時)に"AccessDenied"エラーが発生していることが判明しました。

Lambdaのリソースベースポリシー

原因を調査した結果、LexがLambdaを呼び出す際に必要なリソースベースポリシーが、Lambdaに設定されていなかったことが判明しました。

エラーを解決するために、先ほど紹介したCloudFormationテンプレートに以下を追加します。
この追加により、LexからLambdaを呼び出す権限が付与され、ConnectフローからLexボットを適切に起動できるようになります。

  TestLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt TestLambdaFunction.Arn
      Principal: lexv2.amazonaws.com
      SourceArn: !GetAtt TestLexAlias.Arn
      SourceAccount: !Ref AWS::AccountId

Lambdaのリソースベースのポリシーを追加後、コンソールからは、以下の通り、設定内容がjson形式で確認できます。

cm-hirai-screenshot 2024-09-09 17.59.54

cm-hirai-screenshot 2024-09-09 18.00.03

{
  "StringEquals": {
    "AWS:SourceAccount": "123456789012"
  },
  "ArnLike": {
    "AWS:SourceArn": "arn:aws:lex:ap-northeast-1:123456789012:bot-alias/<ボットID>/<エイリアスID>"
  }
}

Connectから関連付ける場合

Connectインスタンスに直接関連付けるLexやLambdaの場合も紹介します。

Connectインスタンスに直接関連付けるLambdaの場合、CloudFormationでは、今回のようにLambdaのリソースベースポリシーを追加する必要はありません。自動でリソースベースが追加されました。

cm-hirai-screenshot 2024-09-10 8.22.26

{
  "StringEquals": {
    "AWS:SourceAccount": "123456789012"
  },
  "ArnLike": {
    "AWS:SourceArn": "arn:aws:connect:ap-northeast-1:123456789012:instance/xxxxxxxxxx"
  }
}

Connectインスタンスに直接関連付けるLexに関しても、CloudFormationテンプレートで関連付けを行うと、Lexのリソースベースポリシーが自動で追加されていました。

作成したLexのエイリアスからリソースベースポリシー内容が確認できます。
cm-hirai-screenshot 2024-09-10 8.13.41

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "connect-ap-northeast-1-xxxxxxxx",
      "Effect": "Allow",
      "Principal": {
        "Service": "connect.amazonaws.com"
      },
      "Action": [
        "lex:RecognizeText",
        "lex:StartConversation"
      ],
      "Resource": "arn:aws:lex:ap-northeast-1:123456789012:bot-alias/<ボットID>/<エイリアスID>",
      "Condition": {
        "StringEquals": {
          "AWS:SourceAccount": "123456789012"
        },
        "ArnEquals": {
          "AWS:SourceArn": "arn:aws:connect:ap-northeast-1:123456789012:instance/xxxxxxxx"
        }
      }
    }
  ]
}

Amazon Lex ボットを Amazon Connect インスタンスに関連付けると、ボットのリソースベースのポリシーが更新され、Amazon Connect にボットを呼び出すアクセス許可が付与されます。
https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/amazon-lex.html

Lexボットのサービスロール

LexボットがLambdaを呼び出すためには、Lambdaのリソースベースポリシーを追加する必要があると説明しました。
では、LexボットのIAMロールにLambdaを呼び出す権限を追加する方法は適切ではないのでしょうか?

Amazon Lexのドキュメントによると、ユーザーがサービスロールを編集・変更することは推奨されていません。

サービスロールのアクセス許可を変更すると、Amazon Lex V2 の機能が破損する可能性があります。Amazon Lex V2 が指示する場合以外は、サービスロールを編集しないでください。

サービスロールを変更した場合、Lexの機能が正常に動作しない可能性があります。

したがって、Lexのサービスロールを変更するのではなく、Lambdaのリソースベースポリシーを適切に設定することが推奨される方法です。

https://docs.aws.amazon.com/ja_jp/lexv2/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles-service

まとめ

本記事のまとめです。

  1. CloudFormationでLexボットがLambda関数を呼び出すには、リソースベースポリシーをテンプレートで明示的に設定する必要があります。
    cm-hirai-screenshot 2024-09-17 8.34.41
  2. 一方、CloudFormationでConnectインスタンスに関連付けるLexボットやLambdaの場合、リソースベースポリシーは自動的に作成されます。
    cm-hirai-screenshot 2024-09-17 8.30.47

これらの点に注意することで、確実にAWSリソース間の連携を構築できます。

参考

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/connect-lambda-functions.html#add-lambda-function

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.